package com.google.code.mergeinformer.interceptor;

import com.google.code.mergeinformer.controller.ControllerBase;
import com.google.code.mergeinformer.service.AuthorService;
import com.google.code.mergeinformer.service.ProjectService;
import com.google.code.mergeinformer.util.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import org.springframework.web.util.WebUtils;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.StringTokenizer;

/**
 * @author Dmitry Golubev
 */
@Component
public class TemplateModelInterceptor extends HandlerInterceptorAdapter {

    @Autowired
    private HttpSession session;

    @Autowired
    private ProjectService projectService;
    @Autowired
    private AuthorService authorService;

    private static final String RA_AUTHORS = "authors";
    private static final String MA_PROJECTS = "projects";
    private static final String MA_NOTIFICATIONS = "notifications";

    private static final String authorsSeparator = "|";

    private static final List<CookieMapping> cookieMappings = Collections.unmodifiableList(new LinkedList<CookieMapping>() {
        {
            add(new CookieMapping() {

                private List<String> parseAuthors(String value) {
                    if (StringUtils.isBlank(value)) {
                        return Collections.emptyList();
                    }

                    final List<String> authors = new LinkedList<>();
                    // check if there are multiple authors
                    if (value.contains(authorsSeparator)) {
                        final StringTokenizer tokenizer = new StringTokenizer(value, authorsSeparator);
                        while (tokenizer.hasMoreTokens()) {
                            authors.add(tokenizer.nextToken());
                        }
                    } else if (StringUtils.isNotBlank(value)) {
                        authors.add(value);
                    }
                    return authors;
                }

                @Override
                public void map(HttpServletRequest request, HttpServletResponse response, ModelAndView mav) {
                    final String parameter = "author";
                    final String attribute = "authorsFilter";
                    final String cookie = "mergeinformer.filter.author";

                    String value;
                    if (request.getParameterMap().containsKey(parameter)) {
                        value = request.getParameter(parameter).replaceAll("\\s", "");
                    } else {
                        value = "";
                        final Cookie currentCookie = getCookie(request, cookie);
                        if (currentCookie != null) {
                            value = currentCookie.getValue();
                        }
                    }
                    final List<String> authors = parseAuthors(value);
                    response.addCookie(buildCookie(request, cookie, value));
                    mav.addObject(attribute, authors);
                }
            });
            add(new CookieMapping() {
                @Override
                protected void map(HttpServletRequest request, HttpServletResponse response, ModelAndView mav) {
                    final String parameter = "merged";
                    final String attribute = "mergedFilter";
                    final String cookie = "mergeinformer.filter.merged";

                    if (request.getParameterMap().containsKey(parameter)) {
                        final String value = request.getParameter(parameter).trim();
                        Integer parsedValue = 1;
                        try {
                            parsedValue = Integer.parseInt(value);
                        } catch (Exception e) {
                            final String currentValue = getCookieValue(request, cookie);
                            try {
                                parsedValue = Integer.parseInt(currentValue);
                            } catch (Exception ignored) {
                            }
                        }
                        response.addCookie(buildCookie(request, cookie, parsedValue.toString()));
                        mav.addObject(attribute, parsedValue);
                    } else {
                        Integer value = 1;
                        final Cookie currentCookie = getCookie(request, cookie);
                        if (currentCookie != null) {
                            try {
                                value = Integer.parseInt(currentCookie.getValue());
                            } catch (Exception ignored) {
                            }
                        } else {
                            response.addCookie(buildCookie(request, cookie, value.toString()));
                        }
                        mav.addObject(attribute, value.toString());
                    }
                }
            });
        }
    });

    @Override
    public void postHandle(HttpServletRequest request,
                           HttpServletResponse response,
                           Object handler,
                           ModelAndView modelAndView) {
        if (isNotViewRequest(modelAndView)) {
            return;
        }

        for (CookieMapping mapping : cookieMappings) {
            mapping.map(request, response, modelAndView);
        }

        modelAndView.addObject(MA_PROJECTS, projectService.getAllProjects());

        // expose as request attribute to prevent appending those values to the url after redirecting
        request.setAttribute(RA_AUTHORS, authorService.getAllAuthors());

        // pop notifications from session
        synchronized (WebUtils.getSessionMutex(session)) {
            modelAndView.addObject(MA_NOTIFICATIONS, session.getAttribute(ControllerBase.SA_NOTIFICATIONS));
            session.removeAttribute(ControllerBase.SA_NOTIFICATIONS);
        }


        session.setAttribute(ControllerBase.SA_LAST_URL, request.getServletPath() +
                (StringUtils.isNotBlank(request.getQueryString()) ? "?" + request.getQueryString() : ""));
    }

    private boolean isNotViewRequest(ModelAndView modelAndView) {
        return modelAndView == null || modelAndView.getViewName() == null || modelAndView.getViewName().startsWith("redirect:");
    }

    private static abstract class CookieMapping {
        protected  abstract void map(HttpServletRequest request, HttpServletResponse response, ModelAndView mav);

        protected Cookie buildCookie(final HttpServletRequest request, String name, String value) {
            final Cookie cookie = new Cookie(name, value);
            cookie.setPath(request.getContextPath());
            cookie.setMaxAge(Integer.MAX_VALUE);
            return cookie;
        }

        protected Cookie getCookie(HttpServletRequest request, String name) {
            if (request.getCookies() != null) {
                for (Cookie cookie : request.getCookies()) {
                    if (cookie.getName().equals(name)) {
                        return cookie;
                    }
                }
            }
            return null;
        }

        protected String getCookieValue(HttpServletRequest request, String name) {
            final Cookie cookie = getCookie(request, name);
            return cookie != null ? cookie.getValue() : null;
        }
    }
}
